package io.gitee.mingbaobaba.security.core.domain;

import io.gitee.mingbaobaba.security.core.constants.ErrorCodeConstant;
import io.gitee.mingbaobaba.security.core.constants.SecurityConstant;
import io.gitee.mingbaobaba.security.core.exception.SecurityBusinessException;
import io.gitee.mingbaobaba.security.core.factory.SecurityFactory;
import io.gitee.mingbaobaba.security.core.listener.SecurityEventPublishManager;
import io.gitee.mingbaobaba.security.core.utils.DateUtil;
import io.gitee.mingbaobaba.security.core.utils.JsonUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;


/**
 * <p>认证session信息</p>
 *
 * @author yingsheng.ye
 * @version 1.0.0
 * @since 2023/8/27 8:19
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Slf4j
public class SecuritySession implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * sessionId
     */
    private String securitySessionId;

    /**
     * 登录Id
     */
    private String loginId;

    /**
     * 过期时间 单位秒
     */
    private Long timeout;

    /**
     * 当前的SecurityToken
     */
    private SecurityToken currentSecurityToken;

    /**
     * 挂载数据
     */
    private final Map<String, Object> mountData = new HashMap<>();

    /**
     * 创建时间 格式 yyyy-MM-dd HH:mm:ss
     */
    private String createTime;

    /**
     * 更新时间 格式 yyyy-MM-dd HH:mm:ss
     */
    private String updateTime;

    /**
     * 登录的token列表
     */
    private List<SecurityToken> tokenInfoList = new ArrayList<>();

    /**
     * 版本号
     */
    private Long version;

    public SecuritySession(boolean isCreate) {
        if (isCreate) {
            this.createdSecuritySession();
        }
    }

    /**
     * 创建SecuritySession
     */
    public void createdSecuritySession() {
        this.securitySessionId = UUID.randomUUID().toString();
        this.createTime = DateUtil.formatDateTime.apply(new Date());
        //创建通知
        SecurityEventPublishManager.doCreatedSecuritySession(this.securitySessionId);
    }

    /**
     * 销毁SecuritySession
     */
    public void destroySecuritySession() {
        if (Objects.isNull(this.loginId)) {
            return;
        }
        //调用删除
        if (SecurityFactory.getSecurityRepository.get().removeSecuritySessionByLoginId(this.loginId)) {
            //销毁通知
            SecurityEventPublishManager.doDestroySecuritySession(this.securitySessionId);
        }
    }

    // ----------------------------token操作-------------------------------

    /**
     * 更新token信息
     *
     * @param tokenInfo TokenInfo
     */
    public SecuritySession updateTokenInfo(SecurityToken tokenInfo) {
        for (SecurityToken info : this.tokenInfoList) {
            if (info.getToken().equals(tokenInfo.getToken())) {
                if (StringUtils.isNoneBlank(tokenInfo.getState())) {
                    info.setState(tokenInfo.getState());
                }
                if (null != tokenInfo.getDeviceType()) {
                    info.setDeviceType(tokenInfo.getDeviceType());
                }
                if (null != tokenInfo.getTimeout()) {
                    info.setTimeout(tokenInfo.getTimeout());
                }
                if (null != tokenInfo.getActivityTimeout()) {
                    info.setActivityTimeout(tokenInfo.getActivityTimeout());
                }
                info.setUpdateTime(DateUtil.formatDateTime.apply(new Date()));
                break;
            }
        }
        return this;
    }

    /**
     * 更新token状态
     *
     * @param token token
     * @param state 状态
     * @return SecuritySession
     */
    public SecuritySession updateTokenInfoState(String token, String state) {
        SecurityToken tokenInfo = new SecurityToken();
        tokenInfo.setToken(token);
        tokenInfo.setState(state);
        return this.updateTokenInfo(tokenInfo);
    }



    /**
     * token续约
     *
     * @param token token
     * @return SecuritySession
     */
    public SecuritySession renewalToken(String token) {
        if (SecurityFactory.getSecurityRepository.get().renewalTokenByTokenValue(token)) {
            log.debug("续约成功：{}", token);
        }
        return this;
    }


    /**
     * 新增token信息
     *
     * @param tokenInfo TokenInfo
     */
    public SecuritySession addTokenInfo(SecurityToken tokenInfo) {
        this.tokenInfoList.add(tokenInfo);
        return this;
    }

    /**
     * 删除token信息
     *
     * @param token token
     * @return SecuritySession
     */
    public SecuritySession removeTokenInfo(String token) {
        this.tokenInfoList.remove(getTokenInfoByToken(token));
        return this;
    }

    /**
     * 根据tokenValue获取token信息
     *
     * @param token token
     * @return {@link SecurityToken}
     */
    public SecurityToken getTokenInfoByToken(String token) {
        List<SecurityToken> tokenList = tokenInfoList.stream().filter(item -> item.getToken().equals(token))
                .collect(Collectors.toList());
        return tokenList.isEmpty() ? null : tokenList.get(0);
    }

    /**
     * 设置存储信息
     *
     * @param key   key值
     * @param value value值
     * @return SecuritySession
     */
    public SecuritySession setAttribute(String key, Object value) {
        mountData.put(key, value);
        flushSessionStorage();
        return fetchData();
    }

    /**
     * 获取存储信息
     *
     * @param key key值
     * @return Object
     */
    public Object getAttribute(String key) {
        return mountData.get(key);
    }

    /**
     * 获取存储信息
     *
     * @param key    字符串key
     * @param tClass 类型
     * @param <T>    泛型
     * @return 转换后的对象
     */
    public <T> T getAttribute(String key, Class<T> tClass) {
        Object obj = getAttribute(key);
        return JsonUtil.jsonStrToObject(JsonUtil.objectToJsonStr(obj), tClass);
    }

    /**
     * 获取数据
     *
     * @return SecuritySession
     */
    public SecuritySession fetchData() {
        if (Objects.isNull(this.loginId)) {
            return null;
        }
        return SecurityFactory.getSecurityRepository.get()
                .getSecuritySessionByLoginId(this.loginId);
    }

    /**
     * 刷新session到存储
     */
    public void flushSessionStorage() {
        //验证失效的token
        List<SecurityToken> invalidTokenInfoList = new ArrayList<>();
        //最大超时时间(小时)
        int maxTimeoutHour = 48;
        if (!this.getTokenInfoList().isEmpty()) {
            this.getTokenInfoList().forEach(securityToken -> {
                //非可用状态的，且时间超过48小时，视为无效token，进行清理
                if (StringUtils.isNoneBlank(securityToken.getUpdateTime()) &&
                        (!SecurityConstant.TOKEN_STATE_NORMAL.equals(securityToken.getState())
                                && DateUtil.strToLocalDateTime.apply(securityToken.getUpdateTime())
                                .plusHours(maxTimeoutHour).isBefore(LocalDateTime.now())
                        )) {
                    invalidTokenInfoList.add(securityToken);

                }
                //检查并更新token状态
                SecurityToken st = SecurityFactory.getSecurityRepository.get().getSecurityTokenByTokenValue(securityToken.getToken());
                if (Objects.nonNull(st)) {
                    if (!securityToken.getState().equals(st.getState())) {
                        //状态同步
                        st.setState(securityToken.getState());
                        SecurityFactory.getSecurityRepository.get().saveToken(st);
                    }
                    securityToken.setActivityTime(st.getActivityTime());
                }
            });
        }
        invalidTokenInfoList.forEach(item -> this.removeTokenInfo(item.getToken()));

        boolean result = SecurityFactory.getSecurityRepository.get().saveSecuritySession(this);
        if (!result) {
            throw new SecurityBusinessException(ErrorCodeConstant.CODE_SAVE_SESSION_FAILED, "保存session认证数据失败");
        }
        //移除无效的token
        invalidTokenInfoList.forEach(item -> {
            if (SecurityFactory.getSecurityRepository.get().removeTokenByTokenValue(item.getToken())) {
                SecurityEventPublishManager.doRemove(this.getLoginId(), item.getToken(), item.getDeviceType());
            }
        });
    }

}
